#include "media.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <fcntl.h>
#include <stdint.h>
#include "protocol.h"
#include "strs.h"
#include "log.h"

#define FFMPEG_RTSP_CMD_BASE "ffmpeg -rtsp_transport tcp -i "

#ifdef __linux__
#include <sys/wait.h>
#else
#include <Windows.h>
#include <WinSock2.h>
#endif

extern void * my_malloc(size_t size);

static void thread_exit_handler(void *arg)
{
    cmd_base_t *cb = (cmd_base_t *)arg;
    if (cb && cb->repeat) {
        log_info("----------> free cmd_base\n");
        free(cb);
    }
    log_info("thread exit handler\n");
}

static void *cmd_exec_thread_handler(void *arg)
{
    pthread_cleanup_push(thread_exit_handler, arg);

    cmd_base_t *cmd_base = (cmd_base_t *)arg;

    /*
     *  notice: don't take lock in child process. it will be cause deadlock.
     */
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork process error");
    }
    else if (pid == 0) {
        cmd_base->pid = getpid();
        // this function will take lock, don't use it in child process.
        // log_debug("----------> start process(pid: %d)\n", cmd_base->pid);
        execlp("bash", "bash", "-c", cmd_base->cmd, NULL);
    }
    else if (pid > 0) {
        cmd_base->pid = pid;
        int status = 0;
        log_info("------------> wait pid (%d)\n", cmd_base->pid);
        waitpid(cmd_base->pid, &status, WUNTRACED); // 等待进程结束
        if (WIFEXITED(status)) {
            log_debug("waitpid process exit OK\n");
        }
        else if (WEXITSTATUS(status)) {
            log_debug("waitpid process exit: %d\n", WEXITSTATUS(status));
        }
        else if (WIFSIGNALED(status)) {
            // log_debug("waitpid process exit by signal(%s) of process(pid: %d)\n", sys_siglist[(WTERMSIG(status))], getpid());
        }
        else if (WCOREDUMP(status)) {
            log_debug("waitpid process exit by coredump error\n");
        }
    }

    log_debug("----------> cmd: %s is over (pid: %d)\n", cmd_base->cmd, cmd_base->pid);
    cmd_base->pid = -1;
    cmd_base->run = false;

    pthread_cleanup_pop(1);
    return NULL;
}

static int cmd_exec(cmd_base_t *cmd_base)
{
    log_trace("cmd: %s start running\n", cmd_base->cmd);

    int ret = 0;
    cmd_base_t * cb = cmd_base;
    if (cmd_base->repeat) {
        cb = malloc(sizeof(cmd_base_t));
        memcpy(cb, cmd_base, sizeof(cmd_base_t));
    }

    ret = pthread_create(&cb->tid, 0, cmd_exec_thread_handler, cb);
    if (ret) {
        perror("thread create error");
        return MEDIA_RET_THREAD_ERROR;
    }

    pthread_detach(cb->tid);
    return 0;
}

static int user_cmd__(media_t *md, bool run, const char *cmd)
{
    media_ffmpeg_t * mf = (media_ffmpeg_t *)md;
    user_cmd_t *uc = &mf->user_cmd;
    cmd_base_t *cb = (cmd_base_t *)uc;

    if (run && cb->run) {
        return MEDIA_RET_CMD_RUNNING;
    }

    if (md == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    strncpy(uc->cmd, cmd, CMD_LEN);
    strncpy(cb->cmd, cmd, CMD_LEN);

    if (run == false) {
        if (cb->run == true) {
            // todo: kill process
            while (cb->pid <= 0) {
                usleep(10000);
            }
            log_info("send kill to process(pid: %d)\n", cb->pid);
            kill(cb->pid, SIGINT);
        }
        log_trace("kill user cmd\n");
        return 0;
    }

    cb->run = true;
    return cb->exec(&uc->cmd_base);
}

static int rtsp_to_rtmp__(media_t *md, bool run, const char *rtsp_url,
                         const char *rtmp_url, const char *recode)
{
    media_ffmpeg_t * mf = (media_ffmpeg_t *)md;
    rtsp_to_rtmp_t *rtr = &mf->rtsp_to_rtmp;
    cmd_base_t *cb = (cmd_base_t *)rtr;

    if (run && cb->run) {
        return MEDIA_RET_CMD_RUNNING;
    }

    if (md == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    if (run == false) {
        if (cb->run == true) {
            while (cb->pid <= 0) {    // wait for process starting, if not, kill will be failed.
                usleep(10000);
            }
            log_info("send kill to process(pid: %d)\n", cb->pid);
            kill(cb->pid, SIGINT);
        }
        log_trace("kill rtsp_to_rtmp(pid: %d) cmd\n", cb->pid);
        return 0;
    }

    cb->run = true;

    strs_dup2(rtr->rtsp_url, URL_LEN, strs_join_n("", "\"", rtsp_url, "\"", NULL));
    strs_dup2(rtr->rtmp_url, URL_LEN, strs_join_n("", "\"", rtmp_url, "\"", NULL));
    strncpy(rtr->recode, recode, RESOLUTION_LEN);

    log_debug("rtsp_url: %s\n", rtr->rtsp_url);
    log_debug("rtmp_url: %s\n", rtr->rtmp_url);
    log_debug("recode: %s\n", rtr->recode);

    /*
     * ffmpeg -re -i test.mkv  -vcodec h264 -acodec aac
     * -f flv "rtmp://localhost:1935/myapp/video19"
     */
    if (strlen(recode) == 0) {
        strs_dup2(cb->cmd, CMD_LEN, strs_join_n(" ", FFMPEG_RTSP_CMD_BASE,  rtr->rtsp_url,
                        "-vcodec copy -acodec aac -f flv", rtr->rtmp_url, NULL));
    } else {
        strs_dup2(cb->cmd, CMD_LEN, strs_join_n(" ", FFMPEG_RTSP_CMD_BASE, rtr->rtsp_url, "-vcodec h264 -s",
                        recode, "-acodec aac -f flv", rtr->rtmp_url, NULL));
    }
    return cb->exec(cb);
}

static int rtsp_record__(media_t *md, bool run, const char *rtsp_url,
                         const char *out_path)
{
    if (md == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    media_ffmpeg_t * mf = (media_ffmpeg_t *)md;
    rtsp_record_t *rr = &mf->rtsp_record;
    cmd_base_t *cb = (cmd_base_t *)rr;

    if (run && cb->run) {
        return MEDIA_RET_CMD_RUNNING;
    }

    if (md == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    if (run == false) {
        if (cb->run == true) {
            // wait for process starting, if not, kill will be failed.
            while (cb->pid <= 0) {
                usleep(100000);
            }
            log_info("send kill to process(pid: %d)\n", cb->pid);
            kill(cb->pid, SIGINT);
        }
        log_trace("kill rtsp_record(pid: %d) cmd\n", cb->pid);
        return 0;
    }

    cb->run = true;

    strs_dup2(rr->rtsp_url, URL_LEN, strs_join_n("", "\"", rtsp_url, "\"", NULL));
    strncpy(rr->out_path, out_path, OUT_PATH_LEN);
    strs_dup2(cb->cmd, CMD_LEN, strs_join_n(" ", FFMPEG_RTSP_CMD_BASE, rr->rtsp_url,
                                            "-vcodec copy -bsf:v h264_mp4toannexb -y", rr->out_path, NULL));
    return cb->exec(cb);
}

static int rtsp_screenshot__(media_t *md, const char *rtsp_url, const char *out_path)
{
    if (md == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    media_ffmpeg_t * mf = (media_ffmpeg_t *)md;
    rtsp_screenshot_t *rs = &mf->rtsp_screenshot;
    cmd_base_t *cb = (cmd_base_t *)rs;

    strs_dup2(rs->rtsp_url, URL_LEN, strs_join_n("", "\"", rtsp_url, "\"", NULL));
    strncpy(rs->out_path, out_path, OUT_PATH_LEN);
    strs_dup2(cb->cmd, CMD_LEN, strs_join_n(" ", FFMPEG_RTSP_CMD_BASE, rs->rtsp_url,
                                            "-vframes 1 -y", rs->out_path, NULL));
    return cb->exec(cb);
}

static int get_status__(media_t *md, media_status_t *status)
{
    if (md == NULL || status == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    media_ffmpeg_t * mf = (media_ffmpeg_t *)md;
    status->user_cmd_run = mf->user_cmd.cmd_base.run;
    status->rtsp_record_run = mf->rtsp_record.cmd_base.run;
    status->rtsp_to_rtmp_run = mf->rtsp_to_rtmp.cmd_base.run;

    return 0;
}

static int stream_probe__(media_t *md, const char *rtsp_url, char *out_buf, uint32_t size)
{
    if (md == NULL || rtsp_url == NULL || out_buf == NULL) {
        return MEDIA_RET_NULL_PTR;
    }

    char tmp[URL_LEN];
    char *cmd = NULL;
    cmd = strs_join_n(" ", "ffprobe -v quiet -print_format json -show_format -show_streams",
                      strs_dup2(tmp, URL_LEN, strs_join_n("", "\"", rtsp_url, "\"", NULL)), NULL);
    log_trace("probe cmd: %s\n", cmd);

    FILE *fp = popen(cmd, "r");
    uint32_t len = 0;
    uint32_t cnt = 0;
    while ( cnt < size -1  && ((len = fread(out_buf + cnt, 1, size -1 , fp)) > 0) ) {
        cnt += len;
    }

    strs_free(cmd);
    pclose(fp);
    return 0;
}

static int media_ffmpeg_init(media_ffmpeg_t *mf)
{
    pthread_mutex_init(&mf->user_cmd.cmd_base.mutex, NULL);
    pthread_mutex_init(&mf->rtsp_record.cmd_base.mutex, NULL);
    pthread_mutex_init(&mf->rtsp_to_rtmp.cmd_base.mutex, NULL);
    pthread_mutex_init(&mf->rtsp_screenshot.cmd_base.mutex, NULL);

    mf->rtsp_screenshot.cmd_base.repeat = true;

    mf->user_cmd.cmd_base.exec = cmd_exec;
    mf->rtsp_record.cmd_base.exec = cmd_exec;
    mf->rtsp_to_rtmp.cmd_base.exec = cmd_exec;
    mf->rtsp_screenshot.cmd_base.exec = cmd_exec;

    return 0;
}

static void media_ffmpeg_deinit(media_ffmpeg_t *mf)
{
    if (mf == NULL) {
        return ;
    }

    pthread_mutex_destroy(&mf->user_cmd.cmd_base.mutex);
    pthread_mutex_destroy(&mf->rtsp_record.cmd_base.mutex);
    pthread_mutex_destroy(&mf->rtsp_to_rtmp.cmd_base.mutex);
    pthread_mutex_destroy(&mf->rtsp_screenshot.cmd_base.mutex);
}

media_t* media_ffmpeg_new()
{
    media_ffmpeg_t *mf = malloc(sizeof(media_ffmpeg_t));
    if (mf == NULL) {
        return NULL;
    }
    memset(mf, 0, sizeof(media_ffmpeg_t));

    int ret = 0;
    ret = media_ffmpeg_init(mf);
    if (ret) {
        free(mf);
        return NULL;
    }

    media_t *md = (media_t *)mf;
    md->user_cmd = user_cmd__;
    md->rtsp_to_rtmp = rtsp_to_rtmp__;
    md->rtsp_record = rtsp_record__;
    md->rtsp_screenshot = rtsp_screenshot__;
    md->get_status = get_status__;
    md->stream_probe = stream_probe__;

    return md;
}

void media_ffmpeg_delete(media_t *med)
{
    if (med) {
        media_ffmpeg_t *mf = (media_ffmpeg_t *)med;
        media_ffmpeg_deinit(mf);
        free(mf);
    }
}
